// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jhkim

#include "coin2/exchange/sim/order_v1/matching_engine/queue_status.h"

namespace coin2::exchange::sim::order_v1 {

void QueueStatus::AcceptOrder(
    // (HBDM) snapshot case = prebook is not null
    // realtime case = prebook is null
    const LightBook_10_dec12* prebook,
    const LightBook_10_dec12& nextbook) {
  const LightBook_10_dec12* match_book = nullptr;
  LightBook_10_dec12 arrival_time_book;
  if (prebook) {
    // (HBDM) interpolate prebook/(arrival_time_book)/nextbook
    arrival_time_book.InterpolateFrom(
        // arrival time
        time,
        *prebook,
        nextbook);
    match_book = &arrival_time_book;
    is_snapshot = true;
  } else {
    match_book = &nextbook;
    is_snapshot = false;
  }
  SetTakerFill(
      nextbook.fetched_time,
      *match_book,
      &taker_fill);
  accepted = true;

  CHECK_EQ(order_qty, remain_qty) << order_qty << " " << remain_qty;

  if (match_book->IsBidBookAvail()) {
    bid0_price_on_accepted = match_book->bid_price(0);
  }
  if (match_book->IsAskBookAvail()) {
    ask0_price_on_accepted = match_book->ask_price(0);
  }

  if (sign == 1) {
    GetBidQtyAtPrice(*match_book, price, &queue_pos);
  } else if (sign == -1) {
    GetAskQtyAtPrice(*match_book, price, &queue_pos);
  }
  accepted_time = nextbook.fetched_time;

  if (is_snapshot) {
    CHECK(prebook);
    batch_trade.reset(new BatchTradeContainer(
        prebook->fetched_time,
        // order arrival time
        time,
        // next book + 50 ms
        accepted_time + 50 * 1e6));
  }
}

void BatchTradeContainer::MatchAndFlushBatch(QueueStatus* qs) {
  if (trades.size() > 0) {
    double n = trades.size();
    for (int i = 0; i < trades.size(); ++i) {
      double near_end_ratio = 0;
      if (trades.size() == 1) {
        near_end_ratio = 0;
      } else {
        near_end_ratio = i / (n - 1);
      }
      double timestamp = t0 * (1 - near_end_ratio) + tn * near_end_ratio;
      if (qs->cancel_arrival_time > 0 && timestamp > qs->cancel_arrival_time) {
        break;
      }
      if (timestamp > order_arrival_time) {
        qs->ApplyTrade(trades[i]);
      }
    }
    trades.clear();
  }
}

void QueueStatus::ApplyCancel(int64_t fetched_time) {
  if (this->cancel_arrival_time > 0 && this->cancel_confirmed_time == 0) {
    // actual cancel order
    this->cancel_confirmed_time = fetched_time;
  }
}

void QueueStatus::ApplyTrade(const ::fastfeature::FeedMessage& feed) {
  if ((
      feed.trade().side() == ::fastfeature::TRADE_NO_SIDE ||
      feed.trade().side() == ::fastfeature::TRADE_BUY_SIDE)  &&
      this->sign == -1) {
    if (this->price <= PriceType(feed.trade().price_float())
        - feed.trade().price_float() * 1e-4 * makerfill_tolerance_bps) {
      if (this->price < PriceType(feed.trade().price_float()) - 1e-10) {
        this->queue_pos = std::min(this->queue_pos, 0.0);
      }
      this->queue_pos -= feed.trade().qty_float();
    }
  } else if ((
      feed.trade().side() == ::fastfeature::TRADE_NO_SIDE ||
      feed.trade().side() == ::fastfeature::TRADE_SELL_SIDE)  &&
      this->sign == 1) {
    if (this->price >= PriceType(feed.trade().price_float())
        + feed.trade().price_float() * 1e-4 * makerfill_tolerance_bps) {
      if (this->price > PriceType(feed.trade().price_float()) + 1e-10) {
        this->queue_pos = std::min(this->queue_pos, 0.0);
      }
      this->queue_pos -= feed.trade().qty_float();
    }
  }
}

void QueueStatus::ApplyBook(const LightBook_10_dec12& book) {
  if (this->time > book.fetched_time) {
    return;
  }
  if (this->sign == 1) {
    double queue_pos = 0;
    if (GetBidQtyAtPrice(book, this->price, &queue_pos)) {
      this->queue_pos = std::min(this->queue_pos, queue_pos);
      this->queue_pos_ratio = this->queue_pos / queue_pos;
    }
  } else if (this->sign == -1) {
    double queue_pos = 0;
    if (GetAskQtyAtPrice(book, this->price, &queue_pos)) {
      this->queue_pos = std::min(this->queue_pos, queue_pos);
      this->queue_pos_ratio = this->queue_pos / queue_pos;
    }
  }
}

}  // namespace coin2::exchange::sim::order_v1
